Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.

...powered by www.netzwerkartist.de...

 << zurück
Visual C# 2005 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2005

Visual C# 2005
1.320 S., mit 2 CDs, 59,90 Euro
Galileo Computing
ISBN 3-89842-586-X
gp Kapitel 7 Weitere Möglichkeiten von C#
  gp 7.1 Operatorüberladung
    gp 7.1.1 Die Syntax der Operatorüberladung
    gp 7.1.2 Beispiel einer Operatorüberladung
    gp 7.1.3 Überladungsbeispiele
    gp 7.1.4 Benutzerdefinierte Konvertierungen – implizit und explizit
  gp 7.2 Indexer
    gp 7.2.1 Überladen von Indexern
    gp 7.2.2 Parameterbehaftete Eigenschaften
  gp 7.3 Collections (Auflistungen)
    gp 7.3.1 Die elementaren Schnittstellen der Auflistungsklassen
    gp 7.3.2 Die Klasse »ArrayList«
    gp 7.3.3 Das Sortieren der Elemente einer »ArrayList«
    gp 7.3.4 Die Schnittstelle »IDictionary«
    gp 7.3.5 Die Klasse »Hashtable«
    gp 7.3.6 Die Klassen »Queue« und »Stack«
    gp 7.3.7 Objektauflistungen im Überblick
    gp 7.3.8 Benutzerdefinierte Auflistungen
  gp 7.4 Generics – Generische Datentypen
    gp 7.4.1 Die Typproblematik am Beispiel der Klasse »Stack«
    gp 7.4.2 Die Lösung mit einer generischen Klasse
    gp 7.4.3 Typparameter mit Constraints einschränken
    gp 7.4.4 Generische Methoden
    gp 7.4.5 Generics und Vererbung
    gp 7.4.6 Konvertierung von Generics
    gp 7.4.7 Generische Delegate
    gp 7.4.8 Generische Klassen in der .NET-Klassenbibliothek
    gp 7.4.9 Eigene Auflistungen mit »yield« durchlaufen
    gp 7.4.10 Daten durch »null« beschreiben
  gp 7.5 Fortgeschrittene Delegat-Techniken
    gp 7.5.1 Multicast-Delegate
  gp 7.6 Attribute
    gp 7.6.1 Das »Flags«-Attribut
    gp 7.6.2 Anmerkungen zu den Attributen
    gp 7.6.3 Benutzerdefinierte Attribute
  gp 7.7 Unsicherer Programmcode – Zeigertechnik in C#
    gp 7.7.1 Das Schlüsselwort »unsafe«
    gp 7.7.2 Die Deklaration von Zeigern
    gp 7.7.3 Die »fixed«-Anweisung
    gp 7.7.4 Zeigerarithmetik
    gp 7.7.5 Der Operator »->«


Galileo Computing

7.2 Indexer  downtop

In Kapitel 3 haben Sie gelernt, mit Arrays zu arbeiten. Sie wissen, wie Sie ein Array deklarieren und auf die einzelnen Elemente zugreifen können, z.B.:


int[] intArr = new int[10];
intArr[3] = 125;

Mit C# können Sie Klassen und Strukturen so definieren, dass deren Objekte wie ein Array indiziert werden können. Diese neue Erkenntnis sollte Sie eigentlich nicht überraschen, da unter .NET alles als Objekt betrachtet wird, natürlich auch ein Integer-Array. Indizierbare Objekte sind in der Regel Objekte, die als Container für andere Objekte dienen – vergleichbar einem Array. Das .NET Framework stellt uns eine ganze Reihe solcher Klassen zur Verfügung, die als Collections oder Auflistungen bezeichnet werden. Diese agieren ähnlich den uns schon bekannten Arrays, verwalten also Objekte.

Stellen Sie sich vor, Sie würden die Klasse Fußballmannschaft entwickeln. Eine Mannschaft setzt sich aus vielen Einzelspielern zusammen, die innerhalb der Klasse in einem Array vom Typ Spieler verwaltet werden. Instanziieren Sie die Klasse Fußballmannschaft mit


Fußballmannschaft Wacker = new Fußballmannschaft();

wäre es doch zweckdienlich, sich von einem bestimmten Spieler beispielsweise mit der Anweisung


string name = Wacker[2].Zuname;

den Zunamen zu besorgen.

Genau das leistet ein Indexer. Wir übergeben dem Objekt einen Index in eckigen Klammern, der ausgewertet wird und die Referenz auf ein Spieler-Objekt zurückliefert. Darauf können wir mit dem Punktoperator den Zunamen des gewünschten Spielers ermitteln – vorausgesetzt, diese Eigenschaft ist in der Klasse Spieler implementiert.

Ein Indexer ist prinzipiell eine Eigenschaft, die mit this bezeichnet wird und in eckigen Klammern den Typ des Index definiert. Weil sich this immer auf ein konkretes Objekt bezieht, können Indexer niemals static deklariert werden.


// Syntax der Indexer-Definition
Modifikatoren Datentyp this[Parameterliste

Als Modifikatoren sind neben den Zugriffsmodifizierern auch new, virtual, sealed, override und abstract zulässig. Wenn wir uns in Erinnerung rufen, was wir im vorhergehenden Abschnitt über Operatorüberladung gelernt haben, kommt man auch nicht an der Aussage vorbei, dass Indexer eine Überladung des »[]«-Operators sind.

Wenn eine Klasse einen Indexer definiert, darf diese Klasse keine Item-Methode haben, weil interessanterweise ein Indexer als Item-Methode interpretiert wird.

Mit diesem Wissen ausgestattet sollten wir uns nun die einfach gehaltene Implementierung der Klasse Fußballmannschaft ansehen.


// --------------------------------------------------------------
// Beispiel: ...\Kapitel 7\IndexerDemo
// --------------------------------------------------------------
class Program {
  static void Main(string[] args) {
    Fußballmannschaft Wacker = new Fußballmannschaft();
    // Spieler im Team aufnehmen
    Wacker[0] = new Spieler("Fischer", "Udo", 33, "Stürmer");
    Wacker[1] = new Spieler("Müller", "Peter", 25, "Verteidiger");
    Wacker[2] = new Spieler("Mamic", "Miroslav", 22, "Torhüter");
    Wacker[1] = new Spieler("Meier", "Gert", 25, "Stürmer");
    // einen Spieler ausgeben
    Console.WriteLine("Name: {0}, {1}", Wacker[1].Zuname, Wacker[1].Vorname);
    string name = Wacker[2].Zuname;
    Console.ReadLine();
  }
}
class Fußballmannschaft {
  private Spieler[] team = new Spieler[25];
  // Indexer
  public Spieler this[int index] {
    get { return team[index]; }
    set {
      if (team[index] == null)
        team[index] = value;
      else
        Console.WriteLine("Der Index {0} ist bereits belegt", index);
    }
  }
}
class Spieler {
  public string Zuname;
  public string Vorname;
  public int Alter;
  public string Position;
  public Spieler(string zuname, string vorname, int alter, string position) {
    this.Zuname = zuname;
    this.Vorname = vorname;
    this.Alter = alter;
    this.Position = position;
  }
}

Jede Instanz der Klasse Fußballmannschaft verhält sich wie ein Array. Dafür verantwortlich ist der Indexer, der über das Schlüsselwort this deklariert wird und einen Integer entgegennimmt. Der Indexer ist vom Typ Spieler.

Die interne Struktur eines Indexers gleicht der einer Eigenschaftsmethode: Sie enthält einen get- und einen set-Accessor. get wird aufgerufen, wenn unter der Übergabe des int-Parameters Letzterer als Index der Spieler-Arrays ausgewertet wird und den entsprechenden Spieler aus dem privaten Array zurückgibt. Die Zuweisung eines weiteren Spielers hat den Aufruf des set-Zweiges zur Folge.


Galileo Computing

7.2.1 Überladen von Indexern  downtop

In einem herkömmlichen Array erfolgt der Zugriff auf ein Element grundsätzlich über den Index vom Typ Integer. Indexer sind deutlich flexibler, denn sie lassen auch andere Typen zu. In vielen Situationen ist es beispielsweise sinnvoll, anstelle des Index eine Zeichenfolge anzugeben, mit der ein Element identifiziert wird. Meistens handelt es sich dabei um den Namen des Elements. Sind mehrere unterschiedliche Zugriffe wünschenswert, können Indexer nach den bekannten Regeln hinsichtlich Anzahl und Typ der Parameter überladen werden.

Das folgende Beispiel zeigt eine typische Indexer-Überladung. Von einem Objekt der Klasse Tabelle werden maximal 18 Sportvereine verwaltet. Der Tabellenplatz eines Vereins wird dabei durch die Position innerhalb eines string-Arrays beschrieben. Zum Schreiben und Auswerten der Tabelle bieten sich jeweils zwei Alternativen an: über den Tabellenplatz und über den Vereinsnamen.


// --------------------------------------------------------
// Beispiel: ...\Kapitel 7\IndexerÜberladung
// --------------------------------------------------------
class Program {
  static void Main(string[] args) {
    Tabelle tabelle = new Tabelle();
    tabelle[1] = "Wacker Aachen";
    tabelle[4] = "FC Lachhausen";
    tabelle[2] = "Hering Hamburg";
    tabelle[3] = "Martha Groschendorf";
    // Tabellenplatz von FC Lachhausen ändern
    tabelle["FC Lachhausen"] = 2;
    Console.WriteLine("FC Lachhausen steht auf Position {0}", 
                                       tabelle["FC Lachhausen"]);
    // Tabellenplatz von Hering Hamburg ermitteln
    if (tabelle["Hering Hamburg"] != –1) {
      Console.WriteLine("Hering Hamburg ist {0}ter", 
                                      tabelle["Hering Hamburg"]);
    }
    // Tabellenführer ermitteln
    Console.WriteLine("Tabellenführer ist: {0}", tabelle[1]);
    Console.ReadLine();
  }
}
public class Tabelle {
  private string[] verein = new string[18];
  // Indexer, der Zugriff über den Tabellenplatz ermöglicht
  public string this[int tabellenplatz] {
    // liefert den Verein auf eines bestimmten Tabellenplatzes
    get {
      if (tabellenplatz < 1 || tabellenplatz > 18) {
        Console.WriteLine("Die Tabelle hat nur 18 Vereine");
        return "";
      }
      else
        return verein[tabellenplatz – 1];
    }
    // legt den Tabellenplatz eines Vereins fest
    set {
      if (tabellenplatz < 1 || tabellenplatz > 18)
        Console.WriteLine("DDie Tabelle hat nur 18 Vereine");
      else
        verein[tabellenplatz – 1] = value;
    }
  }
  // Indexer, der Zugriff über den Vereinsnamen ermöglicht
  public int this[string vereinsname] {
    // liefert den Tabellenplatz
    get {
      int val = Array.IndexOf(verein, vereinsname);
      if (val == –1) {
        Console.WriteLine("Der Verein spielt in einer anderen Liga");
        return –1;
      }
      else
        return val + 1;
    }
    // legt den Tabellenplatz eines Vereins fest
    set {
      if (value >= 1 && value <= 18)
        verein[value – 1] = vereinsname;
      else
        Console.WriteLine("Die Tabelle hat nur 18 Vereine");
    }
  }
}

Wird dem Aufruf des Indexers ein Vereinsname übergeben, muss geprüft werden, an welcher Position sich dieser in der Tabelle, genau genommen also im Array, befindet. Hier hilft uns die statische Methode IndexOf der Klasse Array weiter. Dieser wird im ersten Parameter der Bezeichner des Arrays übergeben, im zweiten Parameter das gesuchte Element. Wird das Element nicht im Array gefunden, lautet der Rückgabewert –1, ansonsten ist es die Indexposition.

Die Klasse Tabelle ist sehr flexibel. Sie gestattet es, beispielsweise mit


int tabellenposition = tabelle["Hering Hamburg"];

die aktuelle Platzierung des genannten Vereins abzufragen oder durch Übergabe eines int zu erfahren, welcher Verein sich auf dem entsprechenden Tabellenplatz befindet:


string vereinsname = tabelle[1];

Ähnlich einfach könnte ein Verein auch nach Belieben zum Tabellenführer erklärt werden:


tabelle[1] = "Wacker Aachen";

oder


tabelle["Wacker Aachen"] = 1;

Dass dabei der ursprüngliche Tabellenführer »verloren« geht, registrieren wir nur nebenbei. Selbstverständlich könnten wir Code schreiben, der alle Vereine dann um jeweils eine Position in der Tabelle verschiebt. Ehe wir uns aber diese Mühe machen, verrate ich Ihnen schon an dieser Stelle, dass es dazu ähnlich wie Arrays agierende Klassen im .NET Framework gibt, die genau dieses Verhalten zeigen: die Collections, auch als Auflistungen bezeichnet. Später werden Sie mehr darüber erfahren.


Galileo Computing

7.2.2 Parameterbehaftete Eigenschaften  toptop

Eigenschaften sind von Hause aus parameterlos. Mit anderen Worten: Sie können einen Eigenschaftswert nicht in Abhängigkeit von einer oder mehreren Nebenbedingungen setzen. Dieses Manko lässt sich mit Indexer beheben, so dass beispielsweise mit


myObject.TheProperty[2] = 10;

der Eigenschaftswert festgelegt werden kann.

Parametrisierte Eigenschaften sind von Bedeutung, wenn Randbedingungen den von der Eigenschaft beschriebenen Wert beeinflussen. In der fiktiven Eigenschaft TheProperty lautet die Randbedingung »2«. Unter dieser Prämisse soll der Eigenschaft die Zahl 10 zugewiesen werden. Der Code ähnelt ohne Zweifel einem Array und lässt sich auch so interpretieren: Es handelt sich um eine indizierte Sammlung gleichnamiger Eigenschaftselemente.

Wir wollen uns jetzt ansehen, wie in einer Klasse eine parameterbehaftete Eigenschaft realisiert werden kann. Dazu stellen wir uns eine Klasse Car mit einer Eigenschaft Color vor. Ein Car-Objekt beschreibt das Auto eines beliebigen Herstellers. Wir wissen alle, dass die verschiedenen Autoproduzenten unterschiedliche Farbpaletten anbieten – oft modellabhängig. Einen Ferrari gibt es möglicherweise nur in Rot, Gelb oder Schwarz, kaufen Sie einen häufiger vertretenen Typ, können Sie vielleicht unter 20 verschiedenen Farben auswählen. Diese Situation soll die Eigenschaft Color der Car-Klasse beschreiben.

Der Eigenschaft Color wollen wir als Argument eine Zeichenfolge übergeben, die den Hersteller beschreibt. Zurückgeliefert wird daraufhin die Palette der zur Auswahl stehenden Farben. Ein Aufruf könnte dann wie folgt aussehen:


int[] colors = myCar.Color["Mazda"];

Dies sei unser Ziel. Widmen wir uns nun dem Code. Der Teilausdruck


Color["Mazda"]

lässt sich über einen Indexer realisieren. Ein Indexer setzt ein Objekt voraus, denn wie wir wissen, überladen wir den »[]«-Operator in this, dem aktuellen Objekt also. Daraus kann gefolgert werden, dass wir zusätzlich zur Klasse Car eine zweite Klasse definieren müssen, die ihrerseits die Eigenschaft beschreibt. Im Folgenden soll der Name dieser Klasse CarColor lauten.

Wir könnten nun beide Klassen mit


public class Car {/*...*/}
public class CarColor {/*...*/}

festlegen, aber damit käme die eindeutige Zugehörigkeit von CarColor zu Car nicht zum Ausdruck, weil CarColor auch ohne ein zugrunde liegendes Car-Objekt instanziiert werden könnte. Wir wissen aber, dass CarColor in einer direkten Beziehung zu Car steht. Deshalb drängt sich geradezu die Idee auf, CarColor in Car zu verschachteln:


public class Car {
  public class CarColor {/*...*/}
}

Ein Objekt vom Typ CarColor soll einem Benutzer als schreibgeschützte Eigenschaft eines Car-Objekts angeboten werden. Wir ergänzen deshalb die äußere Klassendefinition Car um ein Feld, das die Referenz auf ein Car-Objekt zurückliefert:


public class Car {
  public readonly CarColor Color = new CarColor();
  public class CarColor {/*...*/}
}

Abgesehen von der internen Implementierung der Klasse CarColor können wir Car als fertig betrachten. Der gesamte weitere Programmcode beruht auf der vereinfachenden Annahme, dass sich die von jedem Hersteller angebotene Farbpalette nicht ändert. In der Praxis würde man diese im Car-Konstruktor vermutlich aus einer Datenbank beziehen. Wir legen die Farben jedoch in der Klasse CarColor fest, denn zum Verständnis des Einsatzes der Indexer im Zusammenhang mit parameterbehafteten Eigenschaften ist das völlig ausreichend.

Aus Sicht eines Benutzers sind wir nun an dieser Stelle angelangt:


Car myCar = new Car();
myCar.Color

Die zweite, noch unvollständige Anweisung liefert die Referenz auf ein CarColor-Objekt zurück. Jetzt schlägt die Stunde der Indexer! Zur Vervollständigung der Aufrufsyntax mit dem »[]«-Operator müssen wir im nächsten Schritt in CarColor einen Indexer codieren.

Auch hier vereinfachen wir die Situation und tun so, als würde es nur zwei Autohersteller geben. Beim Aufruf der Color-Eigenschaft wird als Zeichenfolge der Hersteller übergeben, der Rückgabewert sei ein Integer-Array, in dem jede Zahl für eine bestimmte Farbe steht.


public class CarColor {
  public int[] this[string hersteller] {
    get {
      switch(hersteller) {
        case "Rover":
          return new int[]{2, 3, 4, 5};
        case "Mazda":
          return new int[]{1, 2, 5, 6};
        default:
          return new int[]{0};
      }
    }
  }
}

Der Indexer versetzt uns jetzt in die Lage, beim Aufruf der Eigenschaft Color einen Index anzugeben, der als Argument interpretiert wird und den Eigenschaftswert maßgeblich beeinflusst.

Fassen wir den gesamten Code zusammen und schreiben dazu noch einen Testcode, der exakt die Anweisung enthält, die Ausgangspunkt unserer Überlegungen war.


// --------------------------------------------------------------
// Beispiel: ...\Kapitel 7\Eigenschaftsparameter
// --------------------------------------------------------------
class Program {
  static string[] color = new string[]{"Fehleingabe",             "weiss","blau","gelb","rot","schwarz","lila"};
  static void Main(string[] args) {
    Car myCar = new Car();
    int[] arrInt = myCar.Color["Mazda"];
    for(int i = 0; i < arrInt.Length; i++)
      Console.WriteLine("Farbe {0} = {1}", i, color[arrInt[i]] );
    Console.ReadLine();
      }
   }
public class Car {
   public readonly CarColor Color = new CarColor();
   public class CarColor {
     // Indexer
     public int[] this[string hersteller] {
       get {
         switch(hersteller) {
           case "Rover":
             return new int[]{2, 3, 4, 5};
           case "Mazda":
             return new int[]{1, 2, 5, 6};
           default:
             return new int[]{0};
          }
       }
     }
   }
}

Jede der beim Aufruf der Eigenschaft Color zurückgelieferten Integerzahlen ist demselben numerischen Index im string-Array der Testklasse zugeordnet. Die einzige Ausnahme bildet der Index 0, der eine unzulässige Parameterübergabe an die Eigenschaft signalisiert. Mit


int[] arrInt = myCar.Color["Mazda"];

weisen wir den Rückgabewert einem Array zu, das in der darauf folgenden for-Schleife durchlaufen wird und die herstellerspezifische Farbpalette im Befehlsfenster ausgibt.

 << zurück
  
  Zum Katalog
Zum Katalog: Visual C# 2005
Visual C# 2005
bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Fortgeschrittene Programmierung mit Visual C# 2005






 Fortgeschrittene
 Programmierung
 mit Visual C# 2005


Zum Katalog: Einstieg in Visual C# 2005






 Einstieg in
 Visual C# 2005


Zum Katalog: Einstieg in Visual Basic 2005






 Einstieg in
 Visual Basic 2005


Zum Katalog: Visual Basic 2005






 Visual Basic 2005


Zum Katalog: Java ist auch eine Insel






 Java ist auch eine
 Insel


Zum Katalog: Konzepte und Lösungen für Microsoft-Netzwerke






 Konzepte und
 Lösungen für
 Microsoft-Netzwerke


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo








Copyright © Galileo Press 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de